# -*- bazel -*-

load("@drake//tools/skylark:cc.bzl", "cc_binary", "cc_library")
load("@drake//tools/skylark:java.bzl", "java_binary", "java_library")
load("@drake//tools/skylark:py.bzl", "py_library")
load("@drake//tools/skylark:sh.bzl", "sh_binary")
load(
    "@drake//tools/workspace:generate_export_header.bzl",
    "generate_export_header",
)
load("@drake//tools/workspace:generate_file.bzl", "generate_file")

# Everything here is deprecated for removal on 2026-02-01.

licenses([
    "notice",  # BSD-3-Clause
    "restricted",  # LGPL-2.1 AND LGPL-2.1+
])

package(default_visibility = ["//visibility:public"])

config_setting(
    name = "linux",
    constraint_values = ["@platforms//os:linux"],
    visibility = ["//visibility:private"],
)

# Generate header to provide ABI export symbols for LCM.
generate_export_header(
    out = "lcm/lcm_export.h",
    lib = "lcm",
    static_define = "LCM_STATIC",
)

# These options are used when building all LCM C/C++ code.
# They do not flow downstream to LCM-using libraries.
LCM_COPTS = [
    "-w",
    "-std=gnu11",
    "-fvisibility=hidden",
]

LCM_PUBLIC_HEADERS = [
    # Public headers
    "lcm/eventlog.h",
    "lcm/lcm.h",
    "lcm/lcm-cpp.hpp",
    "lcm/lcm-cpp-impl.hpp",
    "lcm/lcm_coretypes.h",
    "lcm/lcm_export.h",  # N.B. This is from generate_export_header above.
    "lcm/lcm_version.h",
]

# The paths within the LCM source archive that make #include statements work.
LCM_PUBLIC_HEADER_INCLUDES = [
    ".",
    # TODO(jwnimmer-tri): The 'lcm' is needed so we can generate lcm_export.h
    # with the correct path and still include it like '#include "lcm_export.h"'
    # from other LCM headers. However, this "pollutes" the include paths of
    # everyone using LCM. Can we do better?
    "lcm",
]

DEPRECATION = "DRAKE DEPRECATED: The @lcm module extension from drake_dep_repositories() is deprecated for removal. Upstream LCM now has native bazel support; use the upstream BUILD rules, instead. The deprecated code will be removed from Drake on or after 2026-02-01."  # noqa

# We only build a shared-library flavor of liblcm.so, because its LGPL-licensed
# and thus should never be linked statically.  Building C++ shared libraries
# in Bazel is not well-supported, but for C code is relatively trivial.  This
# rule creates the runtime artifact -- a loadable shared library with its deps
# linked dynamically.  The `name = "lcm"` rule below provides the compile-time
# artifact that combines the shared library with its headers.
cc_binary(
    name = "libdrake_lcm.so",
    srcs = [
        # Sources
        "lcm/eventlog.c",
        "lcm/lcm.c",
        "lcm/lcm_file.c",
        "lcm/lcm_memq.c",
        "lcm/lcm_mpudpm.c",
        "lcm/lcm_tcpq.c",
        "lcm/lcm_udpm.c",
        "lcm/lcmtypes/channel_port_map_update_t.c",
        "lcm/lcmtypes/channel_to_port_t.c",
        "lcm/ringbuffer.c",
        "lcm/udpm_util.c",
        # Private headers
        "lcm/dbg.h",
        "lcm/ioutils.h",
        "lcm/lcm_internal.h",
        "lcm/lcmtypes/channel_port_map_update_t.h",
        "lcm/lcmtypes/channel_to_port_t.h",
        "lcm/ringbuffer.h",
        "lcm/udpm_util.h",
    ] + LCM_PUBLIC_HEADERS,
    copts = LCM_COPTS,
    includes = LCM_PUBLIC_HEADER_INCLUDES,
    linkopts = select({
        ":linux": [
            "-pthread",  # For lcm_(mp)udpm.c.
            "-Wl,-soname,libdrake_lcm.so",
        ],
        "//conditions:default": [],
    }),
    linkshared = 1,
    visibility = ["//visibility:private"],
    deps = ["@glib//glib"],
)

# This is the compile-time target for liblcm.  See above for details.
cc_library(
    name = "lcm",
    srcs = ["libdrake_lcm.so"],
    hdrs = LCM_PUBLIC_HEADERS,
    includes = LCM_PUBLIC_HEADER_INCLUDES,
    deprecation = DEPRECATION,
)

# This is the header-only library used by generated messages.
cc_library(
    name = "lcm_coretypes",
    hdrs = ["lcm/lcm_coretypes.h"],
    includes = ["."],
    linkstatic = True,
    deprecation = DEPRECATION,
)

cc_binary(
    name = "lcm-logger",
    srcs = [
        "lcm-logger/glib_util.c",
        "lcm-logger/glib_util.h",
        "lcm-logger/lcm_logger.c",
    ],
    copts = LCM_COPTS,
    deps = [
        ":lcm",
        "@glib//glib",
    ],
    deprecation = DEPRECATION,
)

cc_binary(
    name = "lcm-logplayer",
    srcs = [
        "lcm-logger/lcm_logplayer.c",
    ],
    copts = LCM_COPTS,
    deps = [":lcm"],
    deprecation = DEPRECATION,
)

cc_binary(
    name = "lcm-gen",
    srcs = [
        "lcm/lcm_version.h",
        "lcmgen/emit_c.c",
        "lcmgen/emit_cpp.c",
        "lcmgen/emit_csharp.c",
        "lcmgen/emit_go.c",
        "lcmgen/emit_java.c",
        "lcmgen/emit_lua.c",
        "lcmgen/emit_python.c",
        "lcmgen/getopt.c",
        "lcmgen/getopt.h",
        "lcmgen/lcmgen.c",
        "lcmgen/lcmgen.h",
        "lcmgen/main.c",
        "lcmgen/tokenize.c",
        "lcmgen/tokenize.h",
    ],
    copts = LCM_COPTS,
    includes = ["."],
    deps = [
        "@glib//glib",
    ],
    deprecation = DEPRECATION,
)

cc_binary(
    name = "_lcm.so",
    srcs = [
        "lcm-python/module.c",
        "lcm-python/pyeventlog.c",
        "lcm-python/pylcm.c",
        "lcm-python/pylcm.h",
        "lcm-python/pylcm_subscription.c",
        "lcm-python/pylcm_subscription.h",
        "lcm/dbg.h",
    ],
    copts = LCM_COPTS,
    linkshared = 1,
    linkstatic = 1,
    visibility = ["//visibility:private"],
    deps = [
        ":lcm",
        "@drake//tools/workspace/python:cc_headers",
        "@drake//tools/workspace/python:cc_libs",
    ],
)

# Downstream users of lcm-python expect to say "import lcm". However, in the
# sandbox the python package is located at lcm-python/lcm/__init__.py to match
# the source tree structure of LCM; without declaring an `imports = [...]` path
# the import would fail.
#
# Normally we'd add `imports = ["lcm-python"]` to establish a PYTHONPATH at the
# correct subdirectory, and that almost works -- except that the native code's
# RUNPATH entries are not quite correct. Even though `./_lcm.so` (the glue)
# resolves correctly in the sandbox, it needs to then load the main library
# `liblcm.so` to operate. That happens via its RUNPATH, but because the RUNPATH
# is relative, when the lcm module is loaded from the wrong sys.path entry, the
# RUNPATH no longer works.
#
# To repair this, we'll generate our own init file that pre-loads the shared
# library (using python ctypes with the realpath to the shared library) before
# calling the upstream __init__.
generate_file(
    name = "gen/lcm/__init__.py",
    content = """
import ctypes
from pathlib import Path
# The base_dir refers to the base of our package.BUILD.bazel.
_base_dir = Path(__path__[0]).resolve().parent.parent
# Load the native code.
ctypes.cdll.LoadLibrary(_base_dir / '_lcm.so')
# We need to tweak the upstream __init__ before we run it.
_filename = _base_dir / 'lcm-python/lcm/__init__.py'
_text = _filename.read_text(encoding='utf-8')
# Respell where the native code comes from.
_text = _text.replace('from lcm import _lcm', 'import _lcm')
_text = _text.replace('from lcm._lcm import', 'from _lcm import')
exec(compile(_text, _filename, 'exec'))
""",
    visibility = ["//visibility:private"],
)

py_library(
    name = "lcm-python-upstream",
    srcs = ["lcm-python/lcm/__init__.py"],  # Actual code from upstream.
    data = [":_lcm.so"],
    visibility = ["//visibility:private"],
)

py_library(
    name = "lcm-python",
    srcs = ["gen/lcm/__init__.py"],  # Shim, from the genrule above.
    imports = ["gen"],
    deps = [":lcm-python-upstream"],
    deprecation = DEPRECATION,
)

java_library(
    name = "lcm-java",
    srcs = glob(["lcm-java/lcm/**/*.java"]),
    javacopts = [
        # Suppressed until lcm-proj/lcm#159 is fixed.
        "-XepDisableAllChecks",
    ],
    deps = [
        "@drake_maven_internal//:net_sf_jchart2d_jchart2d",
    ],
    deprecation = DEPRECATION,
)

java_binary(
    name = "lcm-logplayer-gui",
    main_class = "lcm.logging.LogPlayer",
    runtime_deps = [":lcm-java"],
    deprecation = DEPRECATION,
)

java_binary(
    name = "lcm-spy",
    jvm_flags = [
        # These flags are copied from the lcm/lcm-java/lcm-spy.sh.in.
        "-Djava.net.preferIPv4Stack=true",
        "-ea",
    ],
    main_class = "lcm.spy.Spy",
    runtime_deps = [
        ":lcm-java",
    ],
    deprecation = DEPRECATION,
)

cc_library(
    name = "lcm_coretypes_non_deprecated",
    deps = [":lcm_coretypes"],
    visibility = ["@drake//tools/workspace/lcm:__subpackages__"],
)

py_library(
    name = "lcm-python_non_deprecated",
    deps = [":lcm-python"],
    visibility = ["@drake//tools/workspace/lcm:__subpackages__"],
)

java_library(
    name = "lcm-java_non_deprecated",
    srcs = glob(["lcm-java/lcm/**/*.java"]),
    javacopts = ["-XepDisableAllChecks"],
    deps = ["@drake_maven_internal//:net_sf_jchart2d_jchart2d"],
    visibility = ["@drake//tools/workspace/lcm:__subpackages__"],
)

sh_binary(
    name = "lcm-gen_non_deprecated",
    srcs = [":lcm-gen"],
)
